
#include "Object.h"

namespace mjc {

	Object::Object(const Kind& kind, const std::wstring& name, const Type& type)
		:kind(kind),
		name(name),
		constantData(NULL),
		variableData(NULL),
		methodData(NULL)
	{
		this->type = new Type(type);
		AllocData();
	}

	Object::Object(const Object& other)
		:kind(other.kind),
		name(other.name),
		constantData(NULL),
		variableData(NULL),
		methodData(NULL)
	{
		type = new Type(*(other.type));
		AllocData();
		CopyData(other);
	}

	Object& Object::operator = (const Object& other)
	{
		if(kind != other.kind)
		{
			FreeData();
			kind = other.kind;
			AllocData();
		}

		name = other.name;
		*type = *other.type;
		CopyData(other);

		return *this;
	}

	Object::~Object()
	{
		delete type;
		FreeData();
	}

	void Object::AllocData()
	{
		switch(kind)
		{
		case K_CONSTANT:
			constantData = new ConstantData;
			break;

		case K_VARIABLE:
			variableData = new VariableData;
			break;

		case K_METHOD:
			methodData = new MethodData;
		}
	}

	void Object::FreeData()
	{
		delete constantData; 
		constantData = NULL;

		delete variableData; 
		variableData = NULL;

		delete methodData; 
		methodData = NULL;
	}

	void Object::CopyData(const Object& object)
	{
		switch(kind)
		{
		case K_CONSTANT:
			*constantData = *object.constantData;
			break;

		case K_VARIABLE:
			*variableData = *object.variableData;
			break;

		case K_METHOD:
			*methodData = *object.methodData;
		}
	}

	Object::ConstantData& Object::GetConstantData()
	{
		assert(kind == K_CONSTANT);
		return *constantData;
	}

	Object::ConstantData Object::GetConstantData() const
	{
		assert(kind == K_CONSTANT);
		return *constantData;
	}

	void Object::SetConstantData(const ConstantData& cd)
	{
		assert(kind == K_CONSTANT);
		*constantData = cd;
	}

	Object::VariableData& Object::GetVariableData()
	{
		assert(kind == K_VARIABLE);
		return *variableData;
	}

	Object::VariableData Object::GetVariableData() const
	{
		assert(kind == K_VARIABLE);
		return *variableData;
	}

	void Object::SetVariableData(const VariableData& vd)
	{
		assert(kind == K_VARIABLE);
		*variableData = vd;
	}

	Object::MethodData& Object::GetMethodData()
	{
		assert(kind == K_METHOD);
		return *methodData;
	}

	Object::MethodData Object::GetMethodData() const
	{
		assert(kind == K_METHOD);
		return *methodData;
	}

	void Object::SetMethodData(const MethodData& md)
	{
		assert(kind == K_METHOD);
		*methodData = md;
	}

	//MethodData
	Object::MethodData::MethodData(const Object::MethodData& md)
		:address(md.address),
		nParameters(md.nParameters),
		locals()
	{
		for(unsigned int i=0; i<md.locals.size(); ++i)
			locals.push_back(new Object(*md.locals[i]));
	}
	
	Object::MethodData& Object::MethodData::operator = (const MethodData& md)
	{
		address = md.address;
		nParameters = md.nParameters;

		for(unsigned int i=0; i<locals.size(); ++i)
			delete locals[i];
		locals.clear();

		for(unsigned int i=0; i<md.locals.size(); ++i)
			locals.push_back(new Object(*md.locals[i]));

		return *this;
	}
	
	Object::MethodData::~MethodData()
	{
		for(unsigned int i=0; i<locals.size(); ++i)
			delete locals[i];
		locals.clear();
	}
	
	std::wostream& operator << (std::wostream& os, const Object& object)
	{
		os << L"name: " << object.name;
		os << L", type: " << *object.type;
		os << L", kind: ";

		switch(object.kind)
		{
		case Object::K_CONSTANT:
			{
				Object::ConstantData cd = object.GetConstantData();
				os << L"constant, value: " << cd.GetValue();
			}
			break;

		case Object::K_METHOD:
			{
				Object::MethodData md = object.GetMethodData();
				os << L"method, address: " << md.GetAddress() << L", nParam: "
					<< md.GetNParameters() << L", locals: (";
				for(unsigned int i=0; i<md.GetLocalsCount(); ++i)
				{
					os << md.GetLocal(i);
				}
				os << L")";
			}
			break;

		case Object::K_VARIABLE:
			{
				Object::VariableData vd = object.GetVariableData();
				os << L"variable, address: " << vd.GetAddress() 
					<< L", level: " << vd.GetLevel();
			}
			break;

		case Object::K_TYPE:
			os << L"type";
			break;

		case Object::K_PROGRAM:
			os << L"program";
			break;
		}

		return os;
	}
}